package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"time"
)

var goPath string
var goSrc string

func init() {
	goPath = os.Getenv("GOPATH")
	if len(goPath) < 1 {
		return
	}
	goPath = strings.Replace(goPath, "\\", "/", -1)
	goSrc = goPath + "/src"
}

func getPkgName(pkgPath string) string {
	pkgPath = strings.Replace(pkgPath, "\\", "/", -1)
	p1 := strings.Replace(pkgPath, goSrc+"/", "", -1)
	return p1
}

func fixDir(dir string) string {
	if runtime.GOOS == "windows" {
		return strings.Replace(dir, "\\", "/", -1)
	}
	return dir
}

func canInstallPkg(pkgDir string) bool {
	files, err := ioutil.ReadDir(pkgDir)
	if err != nil || len(files) == 0 {
		return false
	}
	for _, file := range files {
		if strings.HasSuffix(file.Name(), ".go") {
			return true
		}
	}
	return false
}

func installPkg(pkgName string) {
	fmt.Println("Installing package: ", pkgName)

	cmd := exec.Command("go", "install", pkgName)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Stdin = os.Stdin
	err := cmd.Run()

	if err != nil {
		fmt.Println(err.Error())
	} else {
		fmt.Println("Done")
	}
}

func isGitHgDir(dir string) bool {
	return strings.Contains(dir, "/.git/") ||
		strings.HasSuffix(dir, "/.git") ||
		strings.Contains(dir, "/.hg/") ||
		strings.HasSuffix(dir, "/.hg")
}

func getLatestCode(dir string) {
	var dirInCmd = dir
	if strings.Contains(dir, " ") {
		dirInCmd = "\"" + dir + "\""
	}
	var cmd *exec.Cmd
	if state, err := os.Stat(dir + "/.git"); err == nil && state.IsDir() {

		fmt.Println("Run command: git -C ", dirInCmd, " checkout .")
		cmd = exec.Command("git", "-C", dirInCmd, "checkout", ".")
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Stdin = os.Stdin
		err := cmd.Run()
		if err != nil {
			fmt.Println(err.Error())
		}

		fmt.Println("Run command: git -C ", dirInCmd, " pull")
		cmd = exec.Command("git", "-C", dirInCmd, "pull")
		err = cmd.Run()
		if err != nil {
			fmt.Println(err.Error())
		}
	} else if state, err = os.Stat(dir + "/.hg"); err == nil && state.IsDir() {
		fmt.Println("Run command: hg pull --cwd ", dirInCmd)
		cmd = exec.Command("hg", "pull", "--cwd", dirInCmd)
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Stdin = os.Stdin
		err := cmd.Run()
		if err != nil {
			fmt.Println(err.Error())
		}
	} else {
		return
	}
}

func printUsage() {
	fmt.Fprintln(os.Stderr, "usage: auto-install [-g] [-i]")
	flag.PrintDefaults()
	os.Exit(1)
}

type task struct {
	using     bool
	dir       string
	isGet     bool
	isInstall bool
	id        int
}

func (t *task) run() {
	wg.Add(1)
	t.using = true
	if t.isGet {
		getLatestCode(t.dir)
	}
	if t.isInstall && canInstallPkg(t.dir) {
		installPkg(getPkgName(t.dir))
	}
	t.using = false
	wg.Done()
}

var taskPool []*task
var wg = &sync.WaitGroup{}

func initTaskPool(isGet, isInstall bool) {
	for i := 0; i < 3; i++ {
		taskPool = append(taskPool, &task{
			using:     false,
			isGet:     isGet,
			isInstall: isInstall,
			id:        i,
		})
	}
}

func getFreeTask() *task {
	for {
		for _, t := range taskPool {
			if !t.using {
				t.using = true
				return t
			}
		}
		time.Sleep(100 * time.Millisecond)
	}
}

var excludeDirs = []string{"testdata", "godeps", "vendor", "_testdata", "_fixtures", "_linters", ".github", ".git", ".hg"}

func isExclude(dir string) bool {
	dir = strings.Replace(dir, "\\", "/", -1)
	for _, exDir := range excludeDirs {
		if strings.HasSuffix(dir, "/"+exDir) || strings.Contains(dir, "/"+exDir+"/") {
			return true
		}
	}
	return false
}

func main() {
	flag.Usage = printUsage
	isGet := flag.Bool("g", false, "get the latest code automatically")
	isInstall := flag.Bool("i", false, "install all the packages in $GOPATH automatically")
	flag.Parse()
	if !*isGet && !*isInstall {
		flag.Usage()
	}
	var dirs []string
	filepath.Walk(goSrc, func(root string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		dir := fixDir(root)
		if root == goSrc {
			return nil
		}
		if info.IsDir() && !isGitHgDir(dir) && !isExclude(dir) {
			dirs = append(dirs, dir)
		}
		return nil
	})
	initTaskPool(*isGet, *isInstall)
	for _, dir := range dirs {
		t := getFreeTask()
		t.dir = dir
		go t.run()
	}
	wg.Wait()
}
